#include <cassert>
#include <nice2d/application.h>
#include <nice2d/tilemap.h>
#include <nice2d/components/country.h>
#include <nice2d/components/movepointsearcher.h>
#include <nice2d/components/shortestpathfinder.h>
#include <nice2d/components/attributetable.h>

enum MovePointFlag {
    MovePointFlag_NoWalk = 0,
    MovePointFlag_Our,
    MovePointFlag_Allied,
    MovePointFlag_Anemy,
    MovePointFlag_Ok
};

struct MovePoint {
    MovePointFlag flag = MovePointFlag_Ok;
    sf::Vector2i tilePosition;
};

class MovePointSearcherData
{
public:
    MovePointSearcherData():
        pathFinder(new ShortestPathFinder())
    {
    }
public:
    MovePointFlag getTileFlag(const sf::Vector2i &tilePosition, const sf::Color &country)
    {
        auto tile = map.lock()->getTileByIndex(tilePosition);
        if (!tile || !tile->isWalkable())
            return MovePointFlag_NoWalk;

        auto sprite = tile->getSprite();

        auto tileColor = sf::Color::Transparent;
        if (sprite)
            tileColor = sprite->getSpriteColor();

        if (tileColor == country)
            return MovePointFlag_Our;
        else if (tileColor == sf::Color::Transparent)
            return MovePointFlag_Ok;

        auto countryManager = GET_COMPONENT(Application::getInstance(), CountryManager);
        assert(countryManager);

        auto current = countryManager->getCountry(country);

        if (current->isAllied(tileColor))
            return MovePointFlag_Allied;
        else
            return MovePointFlag_Anemy;
    }

    bool isAdjoinEnemy(const sf::Vector2i &tilePosition,
                       const sf::Color &spriteColor)
    {
        auto tileMap = map.lock();
        auto list = tileMap->getAdjacentTileByTileIndex(tilePosition.x, tilePosition.y);
        auto itr = list.begin();
        while (itr != list.end()) {
            auto flag = getTileFlag(*itr, spriteColor);
            if (flag == MovePointFlag_Anemy)
                return true;
            itr ++;
        }
        return false;
    }

    void searchNextRound(const sf::Vector2i &tilePosition,
                         const sf::Color &spriteColor, int32_t movePower,
                         uint8_t spriteType, bool zoc)
    {
        assert(!map.expired());

        auto tileMap = map.lock();
        auto tile = tileMap->getTileByIndex(tilePosition);

        auto attributeTable = GET_COMPONENT(Application::getInstance(), AttributeTable3D);
        assert(attributeTable);

        auto value = attributeTable->get(spriteType, tile->getTileType(), AttributeTable_Move);
        assert(value.has_value());

        if (movePower > value.value()) {
            movePower -= value.value();

            auto list = tileMap->getAdjacentTileByTileIndex(tilePosition.x, tilePosition.y);
            auto itr = list.begin();
            while (itr != list.end()) {
                auto flag = getTileFlag(*itr, spriteColor);

                if (flag == MovePointFlag_Ok) {
                    auto point = GET_TILE_POSITION(itr->x, itr->y);
                    points.insert(point);

                    auto source = GET_TILE_POSITION(tilePosition.x, tilePosition.y);
                    pathFinder->addEdge(source, point, 1);

                    if (!isAdjoinEnemy(*itr, spriteColor) || zoc)
                        searchNextRound(*itr, spriteColor, movePower, spriteType, zoc);
                } else if (flag == MovePointFlag_Our || flag == MovePointFlag_Allied
                           || (flag == MovePointFlag_Anemy && zoc)) {
                    if (!isAdjoinEnemy(*itr, spriteColor) || zoc)
                        searchNextRound(*itr, spriteColor, movePower, spriteType, zoc);
                }

                itr ++;
            }
        }
    }

    std::set<uint32_t> search(const sf::Vector2i &tilePosition,
                              const sf::Color &country, uint8_t spriteType, bool zoc)
    {
        std::optional<int32_t> movePower;
        auto attributeTable = GET_COMPONENT(Application::getInstance(), AttributeTable2D);
        assert(attributeTable);
        movePower = attributeTable->get(spriteType, AttributeTable_Move);
        assert(movePower.has_value());
        searchNextRound(tilePosition, country, movePower.value(), spriteType, zoc);
        return points;
    }

    std::weak_ptr<TileMap> map;
    sf::Vector2i spritePosition;
    std::set<uint32_t> points;
    std::unique_ptr<ShortestPathFinder> pathFinder;
};

MovePointSearcher::MovePointSearcher(NodePointer object):
    Component(object),
    data(new MovePointSearcherData())
{
    assert(std::dynamic_pointer_cast<TileMap>(object));
}

MovePointSearcher::~MovePointSearcher()
{
}

std::vector<sf::Vector2i> MovePointSearcher::search(const sf::Vector2i &tilePosition,
                                                    const sf::Color &country, uint8_t spriteType, bool zoc)
{
    data->points.clear();
    data->pathFinder->clear();

    std::vector<sf::Vector2i> output;
    auto owner = getOwner();
    if (owner.expired())
        return output;

    data->spritePosition = tilePosition;
    data->map = std::dynamic_pointer_cast<TileMap>(owner.lock());
    auto list = data->search(tilePosition, country, spriteType, zoc);
    auto itr = list.begin();
    while (itr != list.end()) {
        auto id = *itr;
        output.push_back(sf::Vector2i(GET_TILE_X(id), GET_TILE_Y(id)));
        itr ++;
    }
    return output;
}

std::vector<uint32_t> MovePointSearcher::getMovingSequences(const sf::Vector2i &to)
{
    std::vector<uint32_t> output;
    data->pathFinder->search(GET_TILE_POSITION(data->spritePosition.x, data->spritePosition.y),
                             GET_TILE_POSITION(to.x, to.y), output);
    return output;
}
